package org.diy.traffic.sources;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.diy.traffic.updates.TrafficUpdate;

/**
 * This class represents an object which functions as a producer of traffic updates.
 * This class can't itself be instantiated, it must be subclassed.
 *
 * @author Dan Greenblatt
 */
public abstract class TrafficSource {
	
	/** A string which represents the name of this source. Subclasses should set this to something unique
	 * on a per-instance basis, by appending a unique instance name to a subclass base type string.
	 */
	protected String sourceName = "";
	
	/** A time in milliseconds which represents how often this source should be updated **/
	protected long updateFrequency = DEFAULT_UPDATE_FREQUENCY_S * 1000; 
	
	/** A default value for the update frequency of this traffic source. 900 seconds == 15 minutes **/
	protected final static int DEFAULT_UPDATE_FREQUENCY_S = 900;
		
	/** A long representing the time in milliseconds since the epoch when this source was last checked**/
	protected long lastCheck = 0;
	
	protected ArrayList<TrafficUpdate> recentUpdates = new ArrayList<TrafficUpdate>();
	
	/**
	 * Constructor for a TrafficSource object
	 * @param sourceName a unique name to assign to this instance of a TrafficSource object
	 */
	public TrafficSource(String sourceName) {
		// if not specified, use the default update frequency
		this(sourceName, DEFAULT_UPDATE_FREQUENCY_S );
	}
	
	/**
	 * Constructor for a TrafficSource object, includes specification for updateFrequency
	 * @param sourceName a unique name to assign to this instance of a TrafficSource object
	 * @param updateFrequency a time in seconds which represents how often this source should be updated
	 */
	public TrafficSource(String sourceName, int updateFrequency) {
		this.sourceName = sourceName;
		this.updateFrequency = updateFrequency * 1000;
		
	}
	
	/**
	 * Get the unique name for this TrafficSource
	 * @return a unique string name for this traffic source (as assigned by a subclass)
	 */
	public String getName() {
		return sourceName;
	}
	
	/**
	 * Get the update frequency for this TrafficSource
	 * @return a value in milliseconds which represents the update frequency for this TrafficSource
	 */
	public long getUpdateFrequency() {
		return updateFrequency;
	}
	
	/**
	 * Set the update frequency for this TrafficSource
	 * @param freq a value in milliseconds to which this TrafficSource's update frequency should be set
	 */
	public void setUpdateFrequency(long freq) {
		updateFrequency = freq;
	}
	
	
	/**
	 * Utility function to set  the value representing this TrafficSource's last check to now
	 */
	protected void setLastCheckToNow() {
		this.lastCheck = System.currentTimeMillis();
	}
	
	/**
	 * Set this the time representing this TrafficSource's last check to a particular value 
	 * @param aTime A value in milliseconds to which this TrafficSource's lastCheck variable should be set
	 */
	public void setLastCheck(long aTime) {
		this.lastCheck = aTime;
	}
	
	/**
	 * Get the time of last check for this TrafficSource
	 * @return a value representing the time in millseconds since the epoch when this TrafficSource was last checked
	 */
	public long getLastCheck() {
		return this.lastCheck;
	}
	
	/**
	 * Helper function to represent this TrafficSource by it's unique string name
	 */
	@Override
	public String toString() {
		return this.getName();
	}
	
	/**  
	 * Subclasses must implement this function 
	 * <p>
	 * This function should assume that it is time to check this source, and must do all the work required 
	 * to generate a list of TrafficUpdates
	 * @param updateLastCheck boolean flag to represent whether the function should also update the lastChecke time to now
	 * @return A list of TrafficUpdates, can be empty
	 */
	public abstract List<TrafficUpdate> doCheck(boolean updateLastCheck) throws TrafficSourceException;

	/**
	 * A helper function which takes an array of STring elements, and encodes them as an address, substituting
	 * "-" for spaces within an element, and putting "_" in between elements
	 * @param address A list of String elements to encode, can contain as many as necessary (street, city, state, etc.) 
	 * @return an encoded address
	 */
	public static String encodeAddress(String[] address) {
		String address_str = "";
		for (int i=0; i<address.length; i++){
			address_str += address[i].replace(" ", "-");
			if (i < address.length-1) address_str += "_";
		}
		return address_str;
	}
	
	public String processUpdateData(String update) {
		return update;
	}
	
	/**
	 * This function can be used to compare a list of new TrafficUpdates to 
	 * previously cached updates to determine which new ones aren't already
	 * known. The list of new updates are compared against the myUpdates 
	 * ArrayList. New, non-repeated updates are added to myUpdates, and any updates
	 * which are no longer fresn (i.e. no longer appear in newUpdates) are removed
	 * from the cache. 
	 * 
	 * For example if A, B and C are in myUpdates, and B, C and D are in newUpdates,
	 * then when this function is finished running, myUpdates will contain B and C and
	 * new Updates will contain only D.
	 * 
	 * @param newUpdates a list of new TrafficUpdates to compare against cached updates
	 */
	protected void reconcileNewAndRecentUpdates(ArrayList<TrafficUpdate> newUpdates) {
		
		ArrayList<TrafficUpdate> newUpdatesClone = (ArrayList<TrafficUpdate>) newUpdates.clone();
		
		Iterator<TrafficUpdate> new_iterator = newUpdates.iterator();
		while (new_iterator.hasNext()) {
			TrafficUpdate u = new_iterator.next();
			if (recentUpdates.contains(u)) {
				// use the iterator's function to remove the last item, so I don't get a ConcurrentModificationException
				new_iterator.remove();
			} else {
				recentUpdates.add(u);
			}
		}
		
		Iterator<TrafficUpdate> recent_iterator = newUpdates.iterator();
		while (recent_iterator.hasNext()) {
			TrafficUpdate u = recent_iterator.next();
			if (!newUpdatesClone.contains(u)) {
				recentUpdates.remove(u);
			} 
		}
	}
	
	/**
	 * This class represents an error condition that arises as a result of a TrafficSource doing its thing
	 * @author Dan Greenblatt
	 *
	 */
	public class TrafficSourceException extends Exception {
		
		public TrafficSourceException() {super();}
		
		public TrafficSourceException(Exception e) {super(e);}
		
		public TrafficSourceException(String s) {super(s);}
	}
}
